using Assets.Scripts.Common;
using Assets.Scripts.Controllers;
using Assets.Scripts.SO;
using PrimeTween;
using System.Collections;
using UnityEngine;

namespace Assets.Scripts.Entities
{
    internal class Child : MonoBehaviour
    {
        private const int MinimumAngularSpeedToMakeHappy = 10;

        public event System.Action OnChildCollision;

        public State CurrentState { get; private set; }
        public float Happiness { get; private set; }
        public float TimeToGetTipsProgress => Mathf.Clamp01(_jumpingTime / ScriptableChild.TimeToGetTips);

        public ScriptableChild ScriptableChild
        {
            get => _scriptableChild;
            set
            {
                _scriptableChild = value;

                _sprite.sprite = ScriptableChild.Sprite;
                if (ScriptableChild.CustomSize != default)
                {
                    _sprite.transform.localScale = ScriptableChild.CustomSize;
                }

                if (!string.IsNullOrEmpty(ScriptableChild.AdditionalEffect))
                {
                    _effectsContainer.Find(ScriptableChild.AdditionalEffect).gameObject.SetActive(true);
                }


                if (ScriptableChild.AdditionalEffect == "GreenCloud")
                {
                    _sprite.transform.localPosition = new Vector3(0, .134f, 0f);
                    InvokeRepeating(nameof(SpreadUnhappy), 1.5f, 1.5f);
                }
            }
        }

        [SerializeField] private float _speedUpFactorOnChildCollision;
        [SerializeField] private float _pushUpForceOnChildCollision;
        [SerializeField] private string _defaultLayerName;
        [SerializeField] private float _leavingSpeed;
        [SerializeField] private SpriteRenderer _sprite;
        [SerializeField] private Transform _effectsContainer;
        [SerializeField] private LayerMask _jumpingChildsLayerMask;

        [Header("Ground check")]
        [SerializeField] private LayerMask _grounCheckLayerMask;
        [SerializeField] private float _groundCheckDistance = 1.5f;

        [Header("Pay")]
        [SerializeField] private int _payAmount = 100;
        [SerializeField] private int _tips = 30;

        private int _defaultLayerId;
        private Rigidbody2D _rigidbody;
        private Vector2 _leavingDirection;
        private float _jumpingTime;
        private ScriptableChild _scriptableChild;
        private Quaternion _rotationBeforeCorrection;
        private Vector3 _originalScale;

        public void StartJumping()
        {
            CurrentState = State.Jumping;
            _rigidbody.isKinematic = false;
        }

        private void Awake()
        {
            _defaultLayerId = LayerMask.NameToLayer(_defaultLayerName);
            _rigidbody = GetComponent<Rigidbody2D>();

            GetComponent<Throwable>().OnHold += () =>
            {
                UIController.Instance.ShowChildInformation(ScriptableChild);
            };

            GetComponent<Throwable>().OnRelease += () =>
            {
                UIController.Instance.HideChildInformation();
            };

            var scaleFactor = Random.Range(.9f, 1.1f);
            _originalScale = new Vector3(scaleFactor, scaleFactor, 1);

            transform.localScale = _originalScale;
            _rigidbody.mass = scaleFactor;
        }

        private void Start()
        {
            // This is raccoon
        }

        private void Update()
        {
            if (CurrentState == State.Jumping)
            {
                _jumpingTime += Time.deltaTime;
            }

            if (CurrentState == State.Leaving)
            {
                _rigidbody.velocity = _leavingDirection * _leavingSpeed;
            }

            
            if (_rigidbody.angularVelocity > MinimumAngularSpeedToMakeHappy)
            {
                Happiness += Mathf.Abs(_rigidbody.angularVelocity) * ScriptableChild.HappinessOnRotation * Time.deltaTime;
            }

            // .7f - minumum height to make child happy
            if (transform.position.y > -.7f)
            {
                Happiness += ScriptableChild.HappinessOnHeight * Time.deltaTime;
            }
        }

        private void FixedUpdate()
        {
            KeepUpright();
        }

        private void OnCollisionEnter2D(Collision2D collision)
        {
            if (CurrentState == State.Jumping)
            {
                HandleJumping(collision);
                return;
            }

            if (CurrentState == State.Falling && collision.collider.CompareTag(Tags.Ground))
            {
                StartCoroutine(PayAndGo());
            }
        }

        private void HandleJumping(Collision2D collision)
        {
            var oldHapieness = Happiness;

            if (collision.collider.CompareTag(Tags.Child))
            {
                Happiness += ScriptableChild.HappinessOnChildCollision;

                var speed1 = _rigidbody.velocity.sqrMagnitude;
                var speed2 = collision.collider.GetComponent<Rigidbody2D>().velocity.sqrMagnitude;

                if (speed1 > speed2)
                {
                    _rigidbody.velocity *= _speedUpFactorOnChildCollision;
                    _rigidbody.AddForce(new Vector2(0, 1) * _pushUpForceOnChildCollision, ForceMode2D.Impulse);
                    OnChildCollision?.Invoke();

                    EffectsController.Instance.SpawnChildImpactEffect(collision.contacts[0].point);
                    SoundsController.Instance.Play(SoundId.Impact, Random.Range(0.8f, 1.2f));
                }
            }
            else if (collision.collider.CompareTag(Tags.Trampoline))
            {
                if (_rigidbody.velocity.y > 1.2f)
                {
                    Happiness += ScriptableChild.HappinessOnJump;
                    AnimateJump();
                    SoundsController.Instance.Play(SoundId.Impact, Random.Range(0.8f, 1.2f));
                }
            }
            else if (collision.collider.CompareTag(Tags.Ground))
            {
                Happiness += ScriptableChild.HappinessOnGroundCollision;
                AnimateJump();
                SoundsController.Instance.Play(SoundId.Impact, Random.Range(0.8f, 1.2f));
            }

            if (Happiness >= 1)
            {
                gameObject.layer = _defaultLayerId;
                CurrentState = State.Falling;
                SoundsController.Instance.Play(SoundId.Happy);

                var position = transform.position;
                position.z = -5;
                transform.position = position;
            }

            Happiness = Mathf.Clamp01(Happiness);
            var hapienessDelta = Mathf.Abs(Happiness - oldHapieness);

            if (hapienessDelta >= .01)
            {
                if (oldHapieness < Happiness)
                {
                    EffectsController.Instance.SpawnHappyEffect(transform.position);
                }
                else if (oldHapieness > Happiness)
                {
                    EffectsController.Instance.SpawnUnhappyEffect(transform.position);
                }
            }
        }

        private IEnumerator PayAndGo()
        {
            CurrentState = State.Paying;

            yield return new WaitForSeconds(1f);

            StatsController.Instance.HappyChilds++;
            Pay();
            TryPayTips();
            GetComponent<Throwable>().enabled = false;

            CurrentState = State.Leaving;
            _leavingDirection = new Vector2(Random.value > .5f ? 1f : -1f, 0) * Random.Range(.8f, 1.2f);
            gameObject.layer = LayerMask.NameToLayer("WalkingChild");

            Destroy(gameObject, 25f);

            void Pay()
            {
                EconomyController.Instance.ChangeBalance(CurrencyId.Dollars, _payAmount);
                FloatingNumberController.Instance.SpawnPayText(transform.position + new Vector3(0, .5f, 0), _payAmount);
                StatsController.Instance.Income += _payAmount;
                EffectsController.Instance.SpawnPayEffect(transform.position);
                SoundsController.Instance.Play(SoundId.CashRegister);
            }
        }

        private void TryPayTips()
        {
            if (_jumpingTime > ScriptableChild.TimeToGetTips)
            {
                return;
            }

            EconomyController.Instance.ChangeBalance(CurrencyId.Dollars, _tips);
            FloatingNumberController.Instance.SpawnTipsText(transform.position, _tips);
            StatsController.Instance.Tips += _tips;
        }

        private void AnimateJump()
        {
            Tween.Scale(transform, .8f, 1f, .2f, Ease.Linear);
        }

        private void KeepUpright()
        {
            var ground = Physics2D.Raycast(transform.position, new Vector2(0, -1), _groundCheckDistance, _grounCheckLayerMask);
            if (ground.collider == null)
            {
                _rigidbody.constraints = RigidbodyConstraints2D.None;
                return;
            }

            if (_rotationBeforeCorrection == default)
            {
                _rotationBeforeCorrection = transform.rotation;
            }

            _rigidbody.angularVelocity *= .95f;

            var dst = Vector2.Distance(transform.position, ground.point);
            var targetRotation = Quaternion.FromToRotation(Vector2.up, ground.normal);

            // Smoothly rotate towards the calculated rotation
            transform.rotation = Quaternion.Lerp(_rotationBeforeCorrection, targetRotation, _groundCheckDistance - dst / _groundCheckDistance);
        }

        private void SpreadUnhappy()
        {
            if (CurrentState != State.Jumping)
            {
                return;
            }

            var colliders = Physics2D.OverlapCircleAll(transform.position, 3f, _jumpingChildsLayerMask);

            StartCoroutine(Spread());

            IEnumerator Spread()
            {
                foreach (var item in colliders)
                {
                    if (item.TryGetComponent<Child>(out var child) && child != this)
                    {
                        child.Happiness -= 0.15f;
                        EffectsController.Instance.SpawnUnhappyEffect(child.transform.position);
                        yield return new WaitForEndOfFrame();
                    }
                }
            }
        }

        private void OnDrawGizmos()
        {
            Gizmos.DrawLine(transform.position, transform.position + new Vector3(0, -_groundCheckDistance, 0));
        }

        public enum State
        {
            Waiting,
            Jumping,
            Falling,
            Paying,
            Leaving
        }
    }
}
